﻿using System;
using System.Collections.Generic;
using System.Drawing;

using OpenTK;
using OpenTK.Graphics.OpenGL;

using TracedReality;
using TracedReality.Primitive;

namespace DrawingExtensions
{
    public static class DrawingExtensions
    {
        public static void Draw(this Shape shape, double cameraDistance)
        {
            if (shape is Sphere) { Draw((Sphere)shape, cameraDistance); }
            else if (shape is Cylinder) { Draw((Cylinder)shape, cameraDistance); }
            else if (shape is FinitePlane) { Draw((FinitePlane)shape, cameraDistance); }
            else if (shape is Plane) { Draw((Plane)shape, cameraDistance); }
            else if (shape is Checkerboard) { Draw((Checkerboard)shape, cameraDistance); }
            else if (shape is Cube) { Draw((Cube)shape, cameraDistance); }
            else if (shape is Box) { Draw((Box)shape, cameraDistance); }
            else if (shape is Instance) { Draw((Instance)shape, cameraDistance); }
        }

        private static void drawUnitHemishpere(double r, int vstrips, int hstrips)
        {
            for (double j = 0; j < vstrips; j++)
            {
                double aj1 = (j + 1) / vstrips * Math.PI / 2;
                double aj2 = j / vstrips * Math.PI / 2;
                double cosaj1 = Math.Cos(aj1);
                double cosaj2 = Math.Cos(aj2);

                GL.Begin(BeginMode.TriangleStrip);
                for (double i = 0; i <= hstrips; i++)
                {
                    double ai = 2 * i / hstrips * Math.PI;
                    double sinai = Math.Sin(ai);
                    double cosai = Math.Cos(ai);
                    Vector3d p1 = new Vector3d(cosaj1 * cosai, Math.Sin(aj1), cosaj1 * sinai);
                    Vector3d p2 = new Vector3d(cosaj2 * cosai, Math.Sin(aj2), cosaj2 * sinai);

                    GL.Normal3(p1);
                    GL.Vertex3(r * p1);
                    GL.Normal3(p2);
                    GL.Vertex3(r * p2);
                }
                GL.End();
            }
        }

        public static void Draw(this Sphere sphere, double cameraDistance)
        {
            Material m = sphere.material;
            GL.Material(MaterialFace.FrontAndBack, MaterialParameter.AmbientAndDiffuse, (float[])m.diffuse);
            GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Specular, (float[])m.reflective);
            GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Shininess, 100);
            GL.Color3((float[])m.diffuse);

            int strips = (int)(100 * Math.Max(0.10, Math.Min(1, sphere.radius / cameraDistance)));

            GL.PushMatrix();
            GL.Translate(sphere.center.x, sphere.center.y, sphere.center.z);
            drawUnitHemishpere(sphere.radius, strips, strips);
            GL.Scale(1, -1, 1);
            drawUnitHemishpere(sphere.radius, strips, strips);
            GL.PopMatrix();
        }

        private static void drawCirclularAnnulus(double inner, double outer, int pieces, int nmult = 1)
        {
            GL.Begin(BeginMode.TriangleStrip);
            GL.Normal3(0, nmult, 0);
            for (double a = 0; a <= pieces; a++)
            {
                Vector3d p = new Vector3d(Math.Cos(a / pieces * Math.PI * 2),
                    0,
                    Math.Sin(a / pieces * Math.PI * 2));

                GL.Vertex3(inner * p);
                GL.Vertex3(outer * p);
            }
            GL.End();
        }

        private static void drawCylinder(double height, double radius, int pieces, int nmult = 1)
        {
            Vector3d center = new Vector3d(0, 0, 0);
            double hor = height / radius;

            GL.Begin(BeginMode.Quads);
            for (double a = 0; a < pieces; a++)
            {
                double a1 = a / pieces * Math.PI * 2;
                double a2 = (a + 1) / pieces * Math.PI * 2;
                Vector3d p1 = new Vector3d(Math.Cos(a1), 0, Math.Sin(a1));
                Vector3d p2 = p1 + new Vector3d(0, hor, 0);
                Vector3d p3 = new Vector3d(Math.Cos(a2), hor, Math.Sin(a2));
                Vector3d p4 = p3 - new Vector3d(0, hor, 0);

                GL.Normal3(nmult * p1);
                GL.Vertex3(radius * p1);

                GL.Normal3(nmult * p1);
                GL.Vertex3(radius * p2);

                GL.Normal3(nmult * p4);
                GL.Vertex3(radius * p3);

                GL.Normal3(nmult * p4);
                GL.Vertex3(radius * p4);
            }
            GL.End();
        }

        public static void Draw(this Cylinder cylinder, double cameraDistance)
        {
            Material m = cylinder.material;
            GL.Material(MaterialFace.FrontAndBack, MaterialParameter.AmbientAndDiffuse, (float[])m.diffuse);
            GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Specular, (float[])m.reflective);
            GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Shininess, 100);
            GL.Color3((float[])m.diffuse);

            GL.PushMatrix();
            GL.Translate(cylinder.center.x, cylinder.center.y + cylinder.height / 2, cylinder.center.z);

            int strips = (int)(100 * Math.Max(0.10, Math.Min(1, Math.Max(cylinder.innerRad, cylinder.outerRad) / cameraDistance)));
            
            // Top
            drawCirclularAnnulus(cylinder.innerRad, cylinder.outerRad, strips);
            GL.Translate(0, -cylinder.height, 0);
            // Bottom
            drawCirclularAnnulus(cylinder.innerRad, cylinder.outerRad, strips, -1);
            // Inside
            drawCylinder(cylinder.height, cylinder.innerRad, strips, -1);
            // Outside
            drawCylinder(cylinder.height, cylinder.outerRad, strips, -1);

            GL.PopMatrix();
        }


        // How to draw something infinite?
        public static void Draw(this Plane plane, double cameraDistance)
        {
            if (plane is FinitePlane) { Draw((FinitePlane)plane, cameraDistance); }
        }

        public static void Draw(this FinitePlane plane, double cameraDistance)
        {
            Material m = plane.material;
            GL.Material(MaterialFace.FrontAndBack, MaterialParameter.AmbientAndDiffuse, (float[])m.diffuse);
            GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Specular, (float[])m.reflective);
            GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Shininess, 100);
            GL.Color3((float[])m.diffuse);

            GL.PushMatrix();
            GL.Translate(plane.point.x, plane.point.y, plane.point.z);

            GL.Begin(BeginMode.Quads);
            GL.Normal3((double[])plane.normal);
            GL.Vertex3(0, 0, 0);
            GL.Vertex3(plane.d1.x, plane.d1.y, plane.d1.z);
            GL.Vertex3(plane.d1.x + plane.d2.x, plane.d1.y + plane.d2.y, plane.d1.z + plane.d2.z);
            GL.Vertex3(plane.d2.x, plane.d2.y, plane.d2.z);
            GL.End();

            GL.PopMatrix();
        }

        public static void Draw(this Checkerboard board, double cameraDistance)
        {
            foreach (Shape p in board.tree.getAllShapes()) { Draw(p, cameraDistance); }
        }

        public static void Draw(this Box box, double cameraDistance)
        {
            foreach (FinitePlane p in box.faces) { Draw(p, cameraDistance); }
        }

        public static void Draw(this Cube cube, double cameraDistance)
        {
            Draw((Box)cube, cameraDistance);
        }

        public static void Draw(this Instance inst, double cameraDistance)
        {
            GL.PushMatrix();
            GL.Translate(inst.translation.x, inst.translation.y, inst.translation.z);
            Draw(inst.shape, cameraDistance);
            GL.PopMatrix();
        }
    }
}
